package com.cjstudio.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import com.cjstudio.feetgesture.BusinessFunction;
import com.cjstudio.feetgesture.GestureUtils;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

/**
 * This class does all the work for setting up and managing Bluetooth
 * connections with other devices. It has a thread that listens for
 * incoming connections, a thread for connecting with a device, and a
 * thread for performing data transmissions when connected.
 */
public class BluetoothSerialService {
    // Debugging
    private static final String TAG = "BluetoothReadService";
    private static final boolean D = true;
    
    private static final int WINDOW_SIZE = 30;
    private static final int DETECTION_FREQ = 20;


	private static final UUID SerialPortServiceClass_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    // Member fields
	Context mContext;
    private final BluetoothAdapter mAdapter;
    private final Handler mHandler;
    private ConnectThread mConnectThread;
    public ConnectedThread mConnectedThread;
    private int mState;
    
    int action;
    private final Handler mHandler2;


    // Constants that indicate the current connection state
    public static final int STATE_NONE = 0;       // we're doing nothing
    public static final int STATE_LISTEN = 1;     // now listening for incoming connections
    public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
    public static final int STATE_CONNECTED = 3;  // now connected to a remote device

    /**
     * Constructor. Prepares a new BluetoothChat session.
     * @param context  The UI Activity Context
     * @param handler  A Handler to send messages back to the UI Activity
     */
    public BluetoothSerialService(Context context, Handler handler) {
    		mContext = context;
    		mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = STATE_NONE;
        mHandler = handler;
        //!
        
        mHandler2 = new Handler(Looper.getMainLooper());
    }

    /**
     * Set the current state of the chat connection
     * @param state  An integer defining the current connection state
     */
    private synchronized void setState(int state) {
        if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
        mState = state;

        // Give the new state to the Handler so the UI Activity can update
        /*
        mHandler.obtainMessage(BlueTerm.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
        */
    }

    /**
     * Return the current connection state. */
    public synchronized int getState() {
        return mState;
    }

    /**
     * Start the chat service. Specifically start AcceptThread to begin a
     * session in listening (server) mode. Called by the Activity onResume() */
    public synchronized void start() {
        if (D) Log.d(TAG, "start");

        // Cancel any thread attempting to make a connection
        if (mConnectThread != null) {
        	mConnectThread.cancel(); 
        	mConnectThread = null;
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
        	mConnectedThread.cancel(); 
        	mConnectedThread = null;
        }

        setState(STATE_NONE);
    }

    /**
     * Start the ConnectThread to initiate a connection to a remote device.
     * @param device  The BluetoothDevice to connect
     */
    public synchronized void connect(BluetoothDevice device) {
        if (D) Log.d(TAG, "connect to: " + device);

        // Cancel any thread attempting to make a connection
        if (mState == STATE_CONNECTING) {
            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}

        // Start the thread to connect with the given device
        mConnectThread = new ConnectThread(device);
        mConnectThread.start();
        setState(STATE_CONNECTING);
    }

    /**
     * Start the ConnectedThread to begin managing a Bluetooth connection
     * @param socket  The BluetoothSocket on which the connection was made
     * @param device  The BluetoothDevice that has been connected
     */
    public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
        if (D) Log.d(TAG, "connected");

        // Cancel the thread that completed the connection
        if (mConnectThread != null) {
        	mConnectThread.cancel(); 
        	mConnectThread = null;
        }

        // Cancel any thread currently running a connection
        if (mConnectedThread != null) {
        	mConnectedThread.cancel(); 
        	mConnectedThread = null;
        }

        // Start the thread to manage the connection and perform transmissions
        mConnectedThread = new ConnectedThread(socket);
        mConnectedThread.start();

        // Send the name of the connected device back to the UI Activity
        /*
        Message msg = mHandler.obtainMessage(BlueTerm.MESSAGE_DEVICE_NAME);
        Bundle bundle = new Bundle();
        bundle.putString(BlueTerm.DEVICE_NAME, device.getName());
        msg.setData(bundle);
        mHandler.sendMessage(msg);
        */

        setState(STATE_CONNECTED);
    }

    /**
     * Stop all threads
     */
    public synchronized void stop() {
        if (D) Log.d(TAG, "stop");


        if (mConnectThread != null) {
        	mConnectThread.cancel(); 
        	mConnectThread = null;
        }

        if (mConnectedThread != null) {
        	mConnectedThread.cancel(); 
        	mConnectedThread = null;
        }

        setState(STATE_NONE);
    }

    /**
     * Write to the ConnectedThread in an unsynchronized manner
     * @param out The bytes to write
     * @see ConnectedThread#write(byte[])
     */
    public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(out);
    }
    
    /**
     * Indicate that the connection attempt failed and notify the UI Activity.
     */
    private void connectionFailed() {
        setState(STATE_NONE);

        // Send a failure message back to the Activity
        /*
        Message msg = mHandler.obtainMessage(BlueTerm.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(BlueTerm.TOAST, "Unable to connect device");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
        */
    }

    /**
     * Indicate that the connection was lost and notify the UI Activity.
     */
    private void connectionLost() {
        setState(STATE_NONE);

        // Send a failure message back to the Activity
        /*
        Message msg = mHandler.obtainMessage(BlueTerm.MESSAGE_TOAST);
        Bundle bundle = new Bundle();
        bundle.putString(BlueTerm.TOAST, "Device connection was lost");
        msg.setData(bundle);
        mHandler.sendMessage(msg);
        */
    }

    /**
     * This thread runs while attempting to make an outgoing connection
     * with a device. It runs straight through; the connection either
     * succeeds or fails.
     */
    private class ConnectThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final BluetoothDevice mmDevice;

        public ConnectThread(BluetoothDevice device) {
            mmDevice = device;
            BluetoothSocket tmp = null;

            // Get a BluetoothSocket for a connection with the
            // given BluetoothDevice
            try {
                tmp = device.createRfcommSocketToServiceRecord(SerialPortServiceClass_UUID);
            } catch (IOException e) {
                Log.e(TAG, "create() failed", e);
            }
            mmSocket = tmp;
        }

        public void run() {
            Log.i(TAG, "BEGIN mConnectThread");
            setName("ConnectThread");
            
            // Always cancel discovery because it will slow down a connection
            mAdapter.cancelDiscovery();

            // Make a connection to the BluetoothSocket
            try {
                // This is a blocking call and will only return on a
                // successful connection or an exception
                mmSocket.connect();
            } catch (IOException e) {
            		e.printStackTrace();
                connectionFailed();
                // Close the socket
                try {
                    mmSocket.close();
                } catch (IOException e2) {
                    Log.e(TAG, "unable to close() socket during connection failure", e2);
                }
                // Start the service over to restart listening mode
                //BluetoothSerialService.this.start();
                return;
            }

            // Reset the ConnectThread because we're done
            synchronized (BluetoothSerialService.this) {
                mConnectThread = null;
            }

            // Start the connected thread
            connected(mmSocket, mmDevice);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }

    /**
     * This thread runs during a connection with a remote device.
     * It handles all incoming and outgoing transmissions.
     */
    class ConnectedThread extends Thread {
        private final BluetoothSocket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;
        
        private ByteQueue mByteQueue;
        private byte[] mReceiveBuffer;
        
        private char[] mOneLine;
        

        public ConnectedThread(BluetoothSocket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
            
            mReceiveBuffer = new byte[4 * 1024];
            mByteQueue = new ByteQueue(4 * 1024);
            mOneLine = new char[(2+5)*3 + 2 + 100];
        }


        public void run() {
            Log.i(TAG, "BEGIN mConnectedThread");
            byte[] buffer = new byte[1024];
            int bytes;

            // Keep listening to the InputStream while connected
            while (true) {
                try {
                    // Read from the InputStream
                    bytes = mmInStream.read(buffer);
                    read(buffer, bytes);
                    
                } catch (IOException e) {
                    Log.e(TAG, "disconnected", e);
                    connectionLost();
                    break;
                }
            }
        }

        /**
         * Write to the connected OutStream.
         * @param buffer  The bytes to write
         */
        public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);

                // Share the sent message back to the UI Activity
//                mHandler.obtainMessage(BlueTerm.MESSAGE_WRITE, buffer.length, -1, buffer).sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
        }
        
        public void writeChar(int c) {
            byte[] mBuffer = new byte[1];
			mBuffer[0] = (byte)c;

			write(mBuffer);
        }

        public void cancel() {
            try {
                mmSocket.close();
            } catch (IOException e) {
                Log.e(TAG, "close() of connect socket failed", e);
            }
        }
        
        
        
        public void read(byte[] buffer, int length) {
            	try {
            		mByteQueue.write(buffer, 0, length);
            	} catch (InterruptedException e) {
            		
            }
            	update();
        }
        
        private void update() {
            int bytesAvailable = mByteQueue.getBytesAvailable();
            int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
            try {
                int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
                append(mReceiveBuffer, 0, bytesRead);
            } catch (InterruptedException e) {
            }
        }
        
        
        private int pointer = 0;
        
        private boolean newLine = false;
        private boolean nullPrev = false;
        
        private int pointer2 = 0;
//        private float[] xVals, yVals, zVals;
        private int ptr_freq = 0;
        private int ptr_window = 0;
        private float[] xVals = new float[WINDOW_SIZE];
        private float[] yVals = new float[WINDOW_SIZE];
        private float[] zVals = new float[WINDOW_SIZE];
        
        public void append(byte[] buffer, int base, int length) {
            for (int i = 0; i < length; i++) {
                byte b = buffer[base + i];
                try {
                    if (LOG_CHARACTERS_FLAG) {
                        char printableB = (char) b;
                        if (b < 32 || b > 126) {
                            printableB = ' ';
                        }
                        
                        if(b == 10 || b == 13) {
                        		newLine = true;
                        } else {
                        		newLine = false;
                        }
                        
                        if(newLine == true) {
                        		if(mOneLine[0] == 'X' && mOneLine[1] == '=') {
                        			mOneLine[pointer++] = ' ';
                        			
                            		String aLine = "";
                            		for(int j=0; j<mOneLine.length; j++) {
                            			aLine += Character.toString(mOneLine[j]);
                            		}
//                            		Log.w(LOG_TAG, aLine);
                            		
                            		int xVal = 0, yVal = 0, zVal = 0;
                            		
                                 try{
                                	 	String a[] = aLine.split("X=");
                            	        a = a[1].split("Y=");
                            	        xVal = Integer.parseInt(a[0]);
                            	        
                            	        a = a[1].split("Z=");
                            	        yVal = Integer.parseInt(a[0]);
                            	        
                            	        a = a[1].split(" ");
                            	        zVal = Integer.parseInt(a[0]);
                            	        

                                		for(int j=0; j<ptr_window; j++) {
                                			if(j+1 < WINDOW_SIZE) {
                                				xVals[j] = xVals[j+1];
                                				yVals[j] = yVals[j+1];
                                				zVals[j] = zVals[j+1];
                                			}
                                		}
                                		xVals[ptr_window] = xVal;
                                		yVals[ptr_window] = yVal;
                                		zVals[ptr_window] = zVal;
                                		
                                		ptr_window++;
                                		if(ptr_window >= WINDOW_SIZE) ptr_window = WINDOW_SIZE-1;
                                        
                                		if(ptr_freq++ == DETECTION_FREQ) {
                                			if(ptr_window == (WINDOW_SIZE-1)) {
                                				action = BusinessFunction.recognize(xVals, yVals, zVals, 50);
    	                                	    		Log.e("Feet", "Action (" + action + ")  " + GestureUtils.strAction(action));
    	                                	    		
    	                                	    		mHandler2.post(new Runnable() {

															@Override
															public void run() {
																// TODO Auto-generated method stub
		    	                                	    					Toast.makeText(mContext, GestureUtils.strAction(action), Toast.LENGTH_SHORT);
															}
    	                                	    			
    	                                	    		});
    	                                	    		
    	                                	    		
	    	                                	    switch(action) {
	    		                            			case BusinessFunction.ACTION_1: {
	    		                            				
	    		                            			}
	    		                            			case BusinessFunction.ACTION_2: {
	    		                            				
	    		                            			}
	    		                            			case BusinessFunction.ACTION_3: {
	    		                            				
	    		                            			}
	    		                            			default: {
	    		                            				
	    		                            			}
	    		                            		}
                                			}
                                			ptr_freq = 0;
                                		}
                                		
                                		
                                		
                                		
                                		/*
                            	        if(pointer2 < WINDOW_SIZE) {
                            	        	xVals[pointer2] = xVal;
                            	        	yVals[pointer2] = yVal;
                            	        	zVals[pointer2] = zVal;
                            	        	pointer2++;
                            	        } else {
	                        	        	//Feet Gesture Recognition Part!
//	                                	    BusinessFunction.testValue(xVals, yVals, zVals, 50);
                            	        	int action = BusinessFunction.recognize(xVals, yVals, zVals, 50);
	                                	    Log.e("Feet", "Action (" + action + ")  " + GestureUtils.strAction(action));
	                                	    
	                                	    switch(action) {
		                            			case BusinessFunction.ACTION_1: {
		                            				
		                            			}
		                            			case BusinessFunction.ACTION_2: {
		                            				
		                            			}
		                            			case BusinessFunction.ACTION_3: {
		                            				
		                            			}
		                            			default: {
		                            				
		                            			}
		                            		}
	                                	    
                            	        	xVals = new float[WINDOW_SIZE];
                            	        	yVals = new float[WINDOW_SIZE];
                            	        	zVals = new float[WINDOW_SIZE];
                            	        	pointer2 = 0;
                            	        }
                            	        
//                            	        Log.w(LOG_TAG, "X=" + xVal + " Y=" + yVal + " Z=" + zVal);
                                    	*/
                                 } catch(Exception e) {
                                    	e.printStackTrace();
                                 }
                            		
                            		mOneLine = new char[(2+4)*3];
                            		pointer = 0;
                        		} else {
                        			
                        		}
                        } else {
                        		if((printableB != ' ' || printableB == ' ' && nullPrev == false)) {
                        			mOneLine[pointer++] = printableB;
                        		}
                        	}
                        
                        if(printableB == ' ') {
	                    		nullPrev = true;
	                    }
                    }
                } catch (Exception e) {
                    
                }
            }
        }
    }
    

    class ByteQueue {
        public ByteQueue(int size) {
            mBuffer = new byte[size];
        }

        public int getBytesAvailable() {
            synchronized(this) {
                return mStoredBytes;
            }
        }

        public int read(byte[] buffer, int offset, int length)
            throws InterruptedException {
            if (length + offset > buffer.length) {
                throw
                    new IllegalArgumentException("length + offset > buffer.length");
            }
            if (length < 0) {
                throw
                new IllegalArgumentException("length < 0");

            }
            if (length == 0) {
                return 0;
            }
            synchronized(this) {
                while (mStoredBytes == 0) {
                    wait();
                }
                int totalRead = 0;
                int bufferLength = mBuffer.length;
                boolean wasFull = bufferLength == mStoredBytes;
                while (length > 0 && mStoredBytes > 0) {
                    int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
                    int bytesToCopy = Math.min(length, oneRun);
                    System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
                    mHead += bytesToCopy;
                    if (mHead >= bufferLength) {
                        mHead = 0;
                    }
                    mStoredBytes -= bytesToCopy;
                    length -= bytesToCopy;
                    offset += bytesToCopy;
                    totalRead += bytesToCopy;
                }
                if (wasFull) {
                    notify();
                }
                return totalRead;
            }
        }

        public void write(byte[] buffer, int offset, int length)
        throws InterruptedException {
            if (length + offset > buffer.length) {
                throw
                    new IllegalArgumentException("length + offset > buffer.length");
            }
            if (length < 0) {
                throw
                new IllegalArgumentException("length < 0");

            }
            if (length == 0) {
                return;
            }
            synchronized(this) {
                int bufferLength = mBuffer.length;
                boolean wasEmpty = mStoredBytes == 0;
                while (length > 0) {
                    while(bufferLength == mStoredBytes) {
                        wait();
                    }
                    int tail = mHead + mStoredBytes;
                    int oneRun;
                    if (tail >= bufferLength) {
                        tail = tail - bufferLength;
                        oneRun = mHead - tail;
                    } else {
                        oneRun = bufferLength - tail;
                    }
                    int bytesToCopy = Math.min(oneRun, length);
                    System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
                    offset += bytesToCopy;
                    mStoredBytes += bytesToCopy;
                    length -= bytesToCopy;
                }
                if (wasEmpty) {
                    notify();
                }
            }
        }

        private byte[] mBuffer;
        private int mHead;
        private int mStoredBytes;
    }
    
    public static final boolean DEBUG = true;
    public static final boolean LOG_CHARACTERS_FLAG = DEBUG && true;
    
	public static final String LOG_TAG = "BlueTerm";

}
